package org.fujene.diskblock.repli;

import static org.fujene.diskblock.repli.ReplicateContentDiskBlock.LookupType.*;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.fujene.communicate.hash.VirtualContentHash;
import org.fujene.constants.Constants;
import org.fujene.diskblock.DiskBlock;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.memoryblock.repli.ReplicateContentBlock;
import org.fujene.structure.Enumerates;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;

public class ReplicateContentDiskBlock extends DiskBlock{
	public static enum LookupType{
		ALL, ID, NEXT, STR, APP,
	}

	public ReplicateContentDiskBlock(File fileDesc) throws Exception{
		super(fileDesc);
	}

	public ReplicateContentDiskBlock(ReplicateContentBlock memBlock, String dir){
		super(memBlock, dir);
	}

	public ReplicateContentDiskBlock(int ID, File desc, int defaultBodySize, int defaultOptionSize)
			throws Exception{
		super(ID, desc, Constants.MEMORY_TYPE_CONTENTS, defaultBodySize, defaultOptionSize);
	}

	public class ExtentContents{
		private long contentID;
		private LookupPointer nextContent;
		private String str;
		private LookupPointer[] pointers;
		private int bytes;

		// Reading process
		public ExtentContents(int from, LookupType type){
			if(from >= bodySize) return;

			RandomAccessFile inputs = null;
			byte[] val = null;
			try{
				pointers = new LookupPointer[getAppendixTypes()];
				long skipped = (4 * HEADERLEN) + (4 * optionSize) + from;
				inputs = new RandomAccessFile(fileDesc, "r");
				switch(version){
				case Constants.FINALVER: // For back compatibility
					inputs.seek(skipped);

					// Next pointer: (6B)
					skipped += PTR;
					if(type == ALL || type == NEXT){
						val = new byte[PTR];
						inputs.read(val);
						this.nextContent = LookupPointer.fromBytes(val);
					}else inputs.seek(skipped);

					// First ID on invert index: (8B)
					skipped += 8;
					if(type == ALL || type == ID){
						val = new byte[8];
						inputs.read(val);
						this.contentID = ByteConverter.byteToLong(val);
					}else inputs.seek(skipped);

					// Appendix pointer: (6 x (app.)B)
					skipped += PTR * pointers.length;
					if(type == ALL || type == APP){
						val = new byte[PTR];
						for(int i = 0; i < pointers.length; i++){
							inputs.read(val);
							this.pointers[i] = LookupPointer.fromBytes(val);
						}
					}else inputs.seek(skipped);

					int length = 0;
					// string length: (2B)
					skipped += STRLEN;
					if(type == ALL || type == STR){
						val = new byte[STRLEN];
						inputs.read(val);
						length = ByteConverter.byteToInt(val);
					}else inputs.seek(skipped);

					// string
					if(type == ALL || type == STR){
						if(length > 0){
							val = new byte[length];
							inputs.read(val);
							str = new String(val, "UTF-8");
						}else str = Constants.DELETED;
					}

					bytes = PTR + 8 + (pointers.length * PTR) + STRLEN + length;
					break;

				default:
					break;
				}
			}catch(Exception e){}finally{
				if(inputs != null) try{
					inputs.close();
				}catch(IOException e){}
			}
		}

		public long getContentID(){
			return contentID;
		}

		public LookupPointer getNextContent(){
			return nextContent;
		}

		public String getStr(){
			return str;
		}
	}

	public String getContent(int ptr){
		return new ExtentContents(ptr, ALL).getStr();
	}

	public IndexQueue getContentAll(int ptr){
		ExtentContents ptred = new ExtentContents(ptr, ALL);
		return new IndexQueue(Enumerates.INDEX, ptred.getStr(), VirtualContentHash.getContentHash(ptred
				.getContentID()), ptred.getContentID(), 0, ptred.getNextContent(), ptred.bytes);
	}

	public long getContentID(int ptr){
		return new ExtentContents(ptr, ID).getContentID();
	}

	public LookupPointer getNextContentPointer(int ptr){
		ExtentContents ptred = new ExtentContents(ptr, NEXT);
		LookupPointer retPtr = ptred.getNextContent();
		if(retPtr.equals(LookupPointer.EMPTY)) return null;
		else return retPtr;
	}

	public LookupPointer[] getAppendixPointer(int ptr){
		ExtentContents ptred = new ExtentContents(ptr, APP);

		return ptred.pointers;
	}

	// Modify process, using random access file
	public void setContentPointer(int ptr, LookupPointer next){
		RandomAccessFile randomFile = null;
		try{
			randomFile = new RandomAccessFile(fileDesc, "rw");
			// For back-compatibility
			switch(version){
			case Constants.FINALVER:
				randomFile.seek((4 * HEADERLEN) + (4 * optionSize) + ptr);
				randomFile.write(next.toBytes());
			}
		}catch(FileNotFoundException e){}catch(IOException e){}finally{
			try{
				if(randomFile != null) randomFile.close();
			}catch(IOException e){}
		}

	}

	public int getCount(){
		return this.getOption(ReplicateContentBlock.OPT_LEN);
	}

	public int getAppendixTypes(){
		return this.getOption(ReplicateContentBlock.OPT_APP);
	}

	public void delete(int ptr){
		RandomAccessFile writer = null;
		byte[] val = new byte[STRLEN];
		int strlen;
		try{
			writer = new RandomAccessFile(fileDesc, "rw");
			switch(version){
			case Constants.FINALVER:
				writer.seek((4 * HEADERLEN) + (4 * optionSize) + ptr + PTR + 8
						+ (PTR * getOption(ReplicateContentBlock.OPT_APP)));
				writer.read(val);
				strlen = ByteConverter.byteToInt(val);
				strlen *= -1;
				val = ByteConverter.intToByte(strlen, STRLEN);
				writer.seek(writer.getFilePointer() - STRLEN);
				writer.write(val);
				break;
			default:
				break;
			}
		}catch(IOException e){
			System.out.println("Error occurs while writing to file.");
		}finally{
			if(writer != null) try{
				writer.close();
			}catch(IOException e){
				System.out.println("Error occurs while writing to file.");
			}
		}
	}

	@Override
	public int toMemory(MemoryBlock memBlock){
		if(memBlock instanceof ReplicateContentBlock){
			try{
				for(int i = 0; i < ReplicateContentBlock.OPTSIZE; i++)
					memBlock.setOption(i, this.getOption(i));
				memBlock.setBody(this.getBody(0, this.bodySize), 0);
				memBlock.setBodyVacancy(this.bodyVacancy);
			}catch(Exception e){}
		}
		return this.bodySize - this.bodyVacancy;
	}

	@Override
	public byte[] getData(){
		ByteArrayOutputStream stream = new ByteArrayOutputStream(this.bodySize
				+ ReplicateContentBlock.OPTSIZE * 4);

		try{
			stream.write(ByteConverter.intToByte(this.getOption(ReplicateContentBlock.OPT_LEN), 4));
			stream.write(ByteConverter.intToByte(this.getOption(ReplicateContentBlock.OPT_APP), 4));
			stream.write(ByteConverter.intToByte(this.getOption(ReplicateContentBlock.OPT_TYPE), 4));
			stream.write(this.getBody(0, this.bodySize));
		}catch(IOException e){}

		return stream.toByteArray();
	}
}
